/*
 * Copyright 2002-2013 Edward L. Stauff.                  Contact: <EdStauff@gmail.com>.
 * 
 * This file, "Assertion.cs", is part of the "Stauffware.Common" class library.
 * "Stauffware.Common" is free software: you can redistribute it and/or 
 * modify it under the terms of the GNU Lesser Public License as published by the 
 * Free Software Foundation, either version 3 of the License, or (at your option) 
 * any later version.
 * 
 * "Stauffware.Common" is distributed in the hope that it will be useful, 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser Public License for more 
 * details.
 * 
 * You should have received a copy of the GNU Lesser Public License along with 
 * the "Stauffware.Common" sources.  If not, see <http://www.gnu.org/licenses/>.
 */

using System;

namespace Stauffware.Common.Core
{
	#region Static Interface Classes
	//#################################################################################
	/// <summary>
	/// Use these static methods at the top of a method body to explicitly code 
	/// input requirements that can't be checked at compile time.   
	/// </summary>
	/// <remarks>
	/// If an input requirement fails, it's the fault of the code calling the method, 
	/// *not* the fault of the method in which the Require statement appears.
	/// 
	///		***NEVER*** conditionalize the compilation of a Require statement, 
	///		or any other assertion, unless you can prove empirically that the 
	///		assertion has a significant impact on performance!
	/// 
	/// </remarks>
	
	public static class Require
	{
		/// <summary>
		/// Throws an assertion if the given object reference is null.
		/// </summary>
		/// <param name="obj">the object reference that must not be null</param>
		/// <exception cref="InputRequirement">thrown on failure</exception>
		public static void NotNull (Object obj)
		{
			if (obj == null)
				Assert.Throw(new InputRequirement("null object"));
		}

		/// <summary>
		/// Throws an assertion if the given object reference is null.
		/// </summary>
		/// <param name="obj">the object reference that must not be null</param>
        /// <param name="why">additional explanatory information</param>
		/// <exception cref="InputRequirement">thrown on failure</exception>
		public static void NotNull (Object obj, String why)
		{
			if (obj == null)
				Assert.Throw(new InputRequirement("null object: " + why));
		}

		/// <summary>
		/// Throws an assertion if the given object reference is null.
		/// </summary>
		/// <param name="obj">the object reference that must not be null</param>
		/// <exception cref="InputRequirement">thrown on failure</exception>
		public static void NotNothing (Object obj)
		{
			if (obj == null)
				Assert.Throw(new InputRequirement("null object"));
		}

		/// <summary>
		/// Throws an assertion if the given object reference is null.
		/// </summary>
		/// <param name="obj">the object reference that must not be null</param>
		/// <param name="why">additional explanatory information</param>
		/// <exception cref="InputRequirement">thrown on failure</exception>
		public static void NotNothing (Object obj, String why)
		{
			if (obj == null)
				Assert.Throw(new InputRequirement("null object: " + why));
		}

		/// <summary>
		/// Throws an assertion if the given nullable value has no value.
		/// </summary>
		/// <typeparam name="T">the NON-nullable type</typeparam>
		/// <param name="value">the value that must have a value</param>
		/// <exception cref="InputRequirement">thrown on failure</exception>
		public static void HasValue<T> (Nullable<T> value) where T : struct
		{
			if (!value.HasValue)
				Assert.Throw(new InputRequirement("nullable " + typeof(T).FullName + " has no value"));
		}

		/// <summary>
		/// Throws an assertion if the given condition is false.
		/// </summary>
		/// <param name="b">The condition that must be true.</param>
		/// <exception cref="InputRequirement">thrown on failure</exception>
		public static void Condition (bool b)
		{
			if (!b)
				Assert.Throw(new InputRequirement());
		}

		/// <summary>
		/// Throws an assertion if the given condition is false.
		/// </summary>
		/// <param name="b">The condition that must be true.</param>
		/// <param name="descr">additional explanatory information</param>
		/// <exception cref="InputRequirement">thrown on failure</exception>
		public static void Condition (bool b, String descr)
		{
			if (!b)
				Assert.Throw(new InputRequirement(descr));
		}
	}

	//#################################################################################
	/// <summary>
	/// Use these static methods just before returning from a method to explicitly 
	/// code output requirements that can't be checked at compile time.  
	/// If an output requirement fails, it's the fault of the method in which the 
	/// Ensure statement appears.
	/// </summary>
	
	public static class Ensure
	{
		/// <summary>
		/// Throws an assertion if the given nullable value has no value.
		/// </summary>
		/// <typeparam name="T">the NON-nullable type</typeparam>
		/// <param name="value">the value that must have a value</param>
		/// <exception cref="OutputRequirement">thrown on failure</exception>
		public static void HasValue<T> (Nullable<T> value) where T : struct
		{
			if (!value.HasValue)
				Assert.Throw(new OutputRequirement("nullable has no value"));
		}

		/// <summary>
		/// Throws an assertion if the given object reference is null.
		/// </summary>
		/// <param name="obj">the object reference that must not be null</param>
		/// <exception cref="OutputRequirement">thrown on failure</exception>
		public static void NotNull (Object obj)
		{
			if (obj == null)
				Assert.Throw(new OutputRequirement("null object"));
		}

		/// <summary>
		/// Throws an assertion if the given object reference is null.
		/// </summary>
		/// <param name="obj">the object reference that must not be null</param>
		/// <param name="why">additional explanatory information</param>
		/// <exception cref="OutputRequirement">thrown on failure</exception>
		public static void NotNull (Object obj, String why)
		{
			if (obj == null)
				Assert.Throw(new OutputRequirement("null object: " + why));
		}

		/// <summary>
		/// Throws an assertion if the given condition is false.
		/// </summary>
		/// <param name="b">The condition that must be true.</param>
		/// <exception cref="OutputRequirement">thrown on failure</exception>
		public static void Condition (bool b)
		{
			if (!b)
				Assert.Throw(new OutputRequirement());
		}

		/// <summary>
		/// Throws an assertion if the given condition is false.
		/// </summary>
		/// <param name="b">The condition that must be true.</param>
		/// <param name="descr">additional explanatory information</param>
		/// <exception cref="OutputRequirement">thrown on failure</exception>
		public static void Condition (bool b, String descr)
		{
			if (!b)
				Assert.Throw(new OutputRequirement(descr));
		}
	}

	//#################################################################################
	/// <summary>
	/// Use these static methods for explicitly coding assumptions about external 
	/// interfaces over which you have (a) no control and (b) incomplete information.
	/// </summary>
	/// <remarks>
	/// 
	///		***NEVER*** conditionalize the compilation of an Assume statement, 
	///		or any other assertion, unless you can prove empirically that the 
	///		assertion has a significant impact on performance!
	/// 
	/// </remarks>

	public static class Assume
	{
		/// <summary>
		/// Throws an assertion if the given object reference is null.
		/// </summary>
		/// <param name="obj">the object reference that must not be null</param>
		/// <exception cref="Assumption">thrown on failure</exception>
		public static void NotNull (Object obj)
		{
			if (obj == null)
				Assert.Throw(new Assumption("null object"));
		}

		/// <summary>
		/// Throws an assertion if the given object reference is null.
		/// </summary>
		/// <param name="obj">the object reference that must not be null</param>
		/// <param name="why">additional explanatory information</param>
		/// <exception cref="Assumption">thrown on failure</exception>
		public static void NotNull (Object obj, String why)
		{
			if (obj == null)
				Assert.Throw(new Assumption("null object: " + why));
		}

		/// <summary>
		/// Throws an assertion if the given condition is false.
		/// </summary>
		/// <param name="b">The condition that must be true.</param>
		/// <exception cref="Assumption">thrown on failure</exception>
		public static void Condition (bool b)
		{
			if (!b)
				Assert.Throw(new Assumption());
		}

		/// <summary>
		/// Throws an assertion if the given condition is false.
		/// </summary>
		/// <param name="b">The condition that must be true.</param>
		/// <param name="descr">additional explanatory information</param>
		/// <exception cref="Assumption">thrown on failure</exception>
		public static void Condition (bool b, String descr)
		{
			if (!b)
				Assert.Throw(new Assumption(descr));
		}
	}

	//#################################################################################
	/// <summary>
	/// Use these static methods for explicitly coding assumptions that don't meet
	/// the definition of a Require (input requirement), Ensure (output requirement)
	/// or Assume (external interfaces).
	/// </summary>
	/// <remarks>
	/// 
	///		***NEVER*** conditionalize the compilation of an Assert statement, 
	///		or any other assertion, unless you can prove empirically that the 
	///		assertion has a significant impact on performance!
	/// 
	/// </remarks>

	public static class Assert
	{
		/// <summary>
		/// Indicates code that should never be reached.  
		/// Always throws an assertion.
		/// </summary>
		/// <exception cref="Assertion">thrown on failure</exception>
		public static void NotReached ()
		{
			Assert.Throw(new Assertion("unreachable code"));
		}

		/// <summary>
		/// Indicates code that should never be reached.  
		/// Always throws an assertion.
		/// </summary>
		/// <param name="why">additional explanatory information</param>
		/// <exception cref="Assertion">thrown on failure</exception>
		public static void NotReached (String why)
		{
			Assert.Throw(new Assertion("unreachable code: " + why));
		}

		/// <summary>
		/// Use this in the default clause of a switch statement that
		/// shouldn't need a default because there's a case clause
		/// for every legitimate value.
		/// This method always throws an assertion.
		/// </summary>
		/// <param name="switchExpr">the value used in the switch expression</param>
		/// <exception cref="Assertion">thrown on failure</exception>
		public static void NoCaseFor (object switchExpr)
		{
			Assert.Throw(new Assertion("unreachable code: " + 
										ErrorUtils.ValueToString(switchExpr)));
		}

		/// <summary>
		/// Indicates code that has not been implemented yet.   
		/// Always throws an assertion.
		/// This is intended for unimplemented API's, not user features.
		/// </summary>
		/// <exception cref="Assertion">thrown on failure</exception>
		public static void NotImplemented ()
		{
			Assert.Throw(new Assertion("not implemented yet"));
		}

		/// <summary>
		/// Use this for methods that would normally be abstract,
		/// but that can't be made abstract for some reason.
		/// </summary>
		/// <exception cref="Assertion">thrown on failure</exception>
		public static void MustOverride ()
		{
			Assert.Throw(new Assertion("this method must be overridden"));
		}

		/// <summary>
		/// Throws an assertion if the given object reference is null.
		/// </summary>
		/// <param name="obj">the object reference that must not be null</param>
		/// <exception cref="Assertion">thrown on failure</exception>
		public static void NotNull (Object obj)
		{
			if (obj == null)
				Assert.Throw(new Assertion("null object"));
		}

		/// <summary>
		/// Throws an assertion if the given object reference is null.
		/// </summary>
		/// <param name="obj">the object reference that must not be null</param>
		/// <param name="why">additional explanatory information</param>
		/// <exception cref="Assertion">thrown on failure</exception>
		public static void NotNull (Object obj, String why)
		{
			if (obj == null)
				Assert.Throw(new Assertion("null object: " + why));
		}

		/// <summary>
		/// Throws an assertion if the given nullable value has no value.
		/// </summary>
		/// <typeparam name="T">the NON-nullable type</typeparam>
		/// <param name="value">the value that must have a value</param>
		/// <exception cref="Assertion">thrown on failure</exception>
		public static void HasValue<T> (Nullable<T> value) where T : struct
		{
			if (!value.HasValue)
				Assert.Throw(new Assertion("nullable has no value"));
		}

		/// <summary>
		/// Throws an assertion if the given condition is false.
		/// </summary>
		/// <param name="cond">The condition that must be true.</param>
		/// <exception cref="Assertion">thrown on failure</exception>
		public static void Condition (bool cond)
		{
			if (!cond)
				Assert.Throw(new Assertion());
		}

		/// <summary>
		/// Throws an assertion if the given condition is false.
		/// </summary>
		/// <param name="cond">The condition that must be true.</param>
		/// <param name="descr">additional explanatory information</param>
		/// <exception cref="Assertion">thrown on failure</exception>
		public static void Condition (bool cond, String descr)
		{
			if (!cond)
				Assert.Throw(new Assertion(descr));
		}

		/// <summary>
		/// If non-null, this delegate will be called immediately before
		/// an assertion is thrown.  This affects methods in the classes
		/// Assert, Require, Ensure, and Assume.
		/// </summary>
		/// <remarks>
		/// 
		///		*** NEVER *** use this delegate to unconditionally 
		///		disable all exceptions in production code!
		/// 
		/// </remarks>
		public static AssertionPreview PreviewAssertion;

		/// <summary>
		/// All assertions (Assert, Require, Ensure, Assume) get thrown via this method.
		/// </summary>
		/// <param name="a">the assertion to be thrown</param>
		internal static void Throw (Assertion a)
		{
			if (PreviewAssertion != null)
				if (!PreviewAssertion(a))
					return;
			throw a;
		}
	}

	//#################################################################################
	#endregion

	/// <summary>
	/// For previewing assertions before they're thrown.
	/// <see cref="Assert.PreviewAssertion"/>
	/// </summary>
	/// <param name="a">the assertion (exception) that's about to be thrown</param>
	/// <returns>true to throw the assertion, false to not (FOR DEBUGGING ONLY!)</returns>
	public delegate bool AssertionPreview (Assertion a);

	#region Exception Classes

	//#################################################################################
	/// <summary>
	/// Thrown when an assertion fails.
	/// This *ALWAYS* indicates a programming error.
	/// </summary>

	public class Assertion : Exception
	{
		/// <summary>constructor</summary>
		public Assertion ( )
			: base("Assertion Failed")
		{
			ErrorCausedBy.Programmer(this);
		}

		/// <summary>constructor</summary>
		public Assertion (String descr)
			: base("Assertion Failed: " + descr)
		{
			ErrorCausedBy.Programmer(this);
		}

		/// <summary>constructor</summary>
		public Assertion (String descr, Exception inner)
			: base("Assertion Failed: " + descr, inner)
		{
			ErrorCausedBy.Programmer(this);
		}

#if !SILVERLIGHT
		/// required for serialization
		protected Assertion (System.Runtime.Serialization.SerializationInfo info,
							System.Runtime.Serialization.StreamingContext context)
			: base(info, context) { }
#endif
		}

	//#################################################################################
	/// <summary>
	/// Thrown when an input requirement fails.
	/// This *ALWAYS* indicates a programming error.
	/// </summary>

	public class InputRequirement : Assertion
	{
		/// <summary>constructor</summary>
		public InputRequirement (String descr)
			: base("Input Requirement Failed: " + descr)
		{
			ErrorCausedBy.Programmer(this);
		}

		/// <summary>constructor</summary>
		public InputRequirement ()
			: base("Input Requirement Failed")
		{
			ErrorCausedBy.Programmer(this);
		}

#if !SILVERLIGHT
		/// required for serialization
		protected InputRequirement (System.Runtime.Serialization.SerializationInfo info,
							System.Runtime.Serialization.StreamingContext context)
			: base(info, context) { }
#endif
	}

	//#################################################################################
	/// <summary>
	/// Thrown when an output requirement fails.
	/// This *ALWAYS* indicates a programming error.
	/// </summary>

	public class OutputRequirement : Assertion
	{
		/// <summary>constructor</summary>
		public OutputRequirement (String descr)
			: base("Output Requirement Failed: " + descr)
		{
			ErrorCausedBy.Programmer(this);
		}

		/// <summary>constructor</summary>
		public OutputRequirement ()
			: base("Output Requirement Failed")
		{
			ErrorCausedBy.Programmer(this);
		}

#if !SILVERLIGHT
		/// required for serialization
		protected OutputRequirement (System.Runtime.Serialization.SerializationInfo info,
							System.Runtime.Serialization.StreamingContext context)
			: base(info, context) { }
#endif
	}

	//#################################################################################
	/// <summary>
	/// Thrown when an assumption fails.
	/// This *ALWAYS* indicates a programming error.
	/// </summary>

	public class Assumption : Assertion
	{
		/// <summary>constructor</summary>
		public Assumption ( )
			: base("Assumption Failed")
		{
			ErrorCausedBy.Programmer(this);
		}

		/// <summary>constructor</summary>
		public Assumption (String descr)
			: base("Assumption Failed: " + descr)
		{
			ErrorCausedBy.Programmer(this);
		}

		/// <summary>constructor</summary>
		public Assumption (String descr, Exception inner)
			: base("Assumption Failed: " + descr, inner)
		{
			ErrorCausedBy.Programmer(this);
		}

#if !SILVERLIGHT
		/// required for serialization
		protected Assumption (System.Runtime.Serialization.SerializationInfo info,
							System.Runtime.Serialization.StreamingContext context)
			: base(info, context) { }
#endif
	}

	//#################################################################################

	#endregion

}